#include <unistd.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>

/* cpu使用率核心数据信息结构体定义 */
struct my_cpu {
    uint64_t user;       /* 用户态CPU时间 */
    uint64_t nice;       /* 低优先级用户态CPU时间 */
    uint64_t system;     /* 内核态CPU时间 */
    uint64_t idle;       /* 空闲CPU时间 */
    uint64_t iowait;     /* 等待I/O操作CPU时间 */
    uint64_t irq;        /* 硬中断CPU时间 */
    uint64_t softirq;    /* 软中断CPU时间 */
    uint64_t steal;      /* 虚拟机CPU时间 */
    uint64_t guest;      /* 虚拟机低优先级CPU时间 */
    uint64_t guest_nice; /* 虚拟机低优先级用户态CPU时间 */
};

/* 可以处理中断的sleep函数, 睡眠指定时间长度(ms) */
static void my_msleep(int ms)
{
    struct timespec ts;
    int32_t         ret;

    ts.tv_sec  = ms / 1000;
    ts.tv_nsec = (ms % 1000) * 1000000;

    do {
        ret = nanosleep(&ts, &ts);
    } while (ret == -1 && errno == EINTR);

    return;
}

#define MY_CPU_FSCANF_ARGS     11
#define MY_CPU_FSCANF_BUF_SIZE 16
#define MY_CPU_FSCANF(fp, buf, cpu)                                                        \
    fscanf(fp, "%16s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu", buf, &((cpu)->user),        \
        &((cpu)->nice), &((cpu)->system), &((cpu)->idle), &((cpu)->iowait), &((cpu)->irq), \
        &((cpu)->softirq), &((cpu)->steal), &((cpu)->guest), &((cpu)->guest_nice))

/* 计算cpu使用率, iowait也算在空闲cpu时间里面 */
#define MY_CPU_TOTAL(cpu)                                                                   \
    ((cpu)->user + (cpu)->nice + (cpu)->system + (cpu)->idle + (cpu)->iowait + (cpu)->irq + \
        (cpu)->softirq + (cpu)->steal + (cpu)->guest + (cpu)->guest_nice)
#define MY_CPU_IDLE(cpu)  ((cpu)->idle + (cpu)->iowait)
#define MY_CPU_USAGE(cpu) (MY_CPU_TOTAL(cpu) - MY_CPU_IDLE(cpu))

#define MY_CPU_NUM      4                /* 4核cpu */
#define MY_CPU_DATA_NUM (MY_CPU_NUM + 1) /* cpu数据个数 */

/* 处理两次采集的cpu使用数据, 然后以合适的格式打印出去 */
static void my_cpu_deal_data(struct my_cpu *cpu_info, struct my_cpu *cpu_info_old)
{
    int32_t  i, len;
    uint64_t diff_total[MY_CPU_DATA_NUM], diff_idle[MY_CPU_DATA_NUM], diff_usage[MY_CPU_DATA_NUM];
    char     buffer[1024];

    for (i = 0; i < MY_CPU_DATA_NUM; i++) {
        diff_total[i] = MY_CPU_TOTAL(&cpu_info[i]) - MY_CPU_TOTAL(&cpu_info_old[i]);
        diff_idle[i]  = MY_CPU_IDLE(&cpu_info[i]) - MY_CPU_IDLE(&cpu_info_old[i]);
        diff_usage[i] = diff_total[i] - diff_idle[i];
    }

    len = snprintf(buffer, sizeof(buffer),
        "CPU total: %lu, idle: %lu, usage: %lu, %.2f%%: ", diff_total[0], diff_idle[0],
        diff_usage[0], (double)diff_usage[0] * 100 / diff_total[0]);
    for (i = 1; i < MY_CPU_DATA_NUM; i++) {
        len += snprintf(buffer + len, sizeof(buffer) - len, "core%d %.2f%%, ", (i - 1),
            (double)diff_usage[i] * 100 / diff_total[i]);
    }

    printf("%s\n", buffer);
    return;
}

/**
 * 获取指定CPU核的使用率, 通过读取/proc/stat文件数据实现.
 * 对于/proc/stat文件的具体解释, 可参考Linux kernel代码: fs/proc/stat.c.
 * 不同版本的Linux内核, /proc/stat文件的格式可能会有所不同(有问题请分析源码).
 */
bool get_cpu_usage(uint32_t interval_ms)
{
    FILE         *fp;
    int32_t       ret, i;
    struct my_cpu cpu_info[MY_CPU_DATA_NUM], cpu_info_old[MY_CPU_DATA_NUM];
    char          buf[MY_CPU_FSCANF_BUF_SIZE + 1];

    /* 打开全局cpu状态信息虚拟文件 */
    fp = fopen("/proc/stat", "r");
    if (fp == NULL) {
        printf("Open /proc/stat file error, %s(%d)\n", strerror(errno), errno);
        return false;
    }

    /* 先读取一次原始数据, 然后后续连续间隔指定时间获取数据, 并实时输出使用率 */
    for (i = 0; i < MY_CPU_DATA_NUM; i++) {
        if (MY_CPU_FSCANF(fp, buf, &cpu_info_old[i]) != MY_CPU_FSCANF_ARGS) {
            goto error;
        }
    }

    /* 循环读取cpu使用情况, 并且打印数据 */
    do {
        /* 移动fp文件指针到文件起始处, 刷新文件缓冲, 持续读取数据 */
        fflush(fp);
        if (fseek(fp, 0, SEEK_SET) != 0) {
            printf("Seek /proc/stat file error, %s(%d)\n", strerror(errno), errno);
            goto error;
        }
        my_msleep(interval_ms);

        /* 循环读取所有核的cpu数据 */
        for (i = 0; i < MY_CPU_DATA_NUM; i++) {
            if (MY_CPU_FSCANF(fp, buf, &cpu_info[i]) != MY_CPU_FSCANF_ARGS) {
                goto error;
            }
        }
        my_cpu_deal_data(cpu_info, cpu_info_old);
        memcpy(cpu_info_old, cpu_info, sizeof(cpu_info_old));
    } while (1);

error:
    fclose(fp);

    return true;
}

/* 给定间隔时间 */
int main(int argc, char *argv[])
{
    uint32_t interval_ms;

    if (argc != 2) {
        printf("Usage: %s <interval_ms>\n", argv[0]);
        return -1;
    }

    interval_ms = atoi(argv[1]);
    get_cpu_usage(interval_ms);

    return 0;
}

